package org.hepeng.workx.util.proxy;


import javassist.util.proxy.Proxy;
import org.apache.commons.collections.CollectionUtils;
import org.hepeng.workx.exception.ApplicationRuntimeException;
import org.hepeng.workx.util.ClassUtils;
import org.hepeng.workx.util.ConstructorUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author he peng
 */
public class JavassistProxyFactory implements ProxyFactory {

    @Override
    public Object createProxy(Object target , ProxyInvocationHandler invocationHandler, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        return createProxy(target.getClass() , invocationHandler , constructorArgTypes , constructorArgs);
    }

    @Override
    public Object createProxy(Class targetClass, ProxyInvocationHandler invocationHandler, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        return createProxy(targetClass , null , invocationHandler , constructorArgTypes , constructorArgs);
    }

    @Override
    public Object createProxy(Class targetClass, List<Class<?>> interfaces, ProxyInvocationHandler invocationHandler, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        javassist.util.proxy.ProxyFactory proxyFactory = new javassist.util.proxy.ProxyFactory();
        if (! ClassUtils.isInterface(targetClass)) {
            proxyFactory.setSuperclass(targetClass);
            proxyFactory.setInterfaces(ConstructorUtils.argTypeToArray(interfaces));
        } else {
            if (Objects.isNull(interfaces)) {
                interfaces = new ArrayList<>();
            }
            interfaces.add(targetClass);
            interfaces = new ArrayList<>(new HashSet<>(interfaces));
            proxyFactory.setInterfaces(ConstructorUtils.argTypeToArray(interfaces));
        }

        Proxy proxy;
        try {
            Object[] args = ConstructorUtils.argToArray(constructorArgs);
            Class<?>[] argTypes = ConstructorUtils.argTypeToArray(constructorArgTypes);
            proxy = (Proxy) proxyFactory.create(argTypes, args);
            proxy.setHandler(
                    (self, thisMethod, proceed, args1) -> {
                        JavassistInvocation invocation = new JavassistInvocation(self , thisMethod , args1 , proceed);
                        return invocationHandler.invoke(invocation);
                    });
        } catch (Throwable t) {
            throw new ApplicationRuntimeException(t);
        }

        return proxy;
    }
}
